//============================================================================
// Copyright (C) 2013 Brett R. Jones
//
// Issued to public domain 2013
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================
#include "VxThread.h"
#include "VxGlobals.h"
#include "vx_debug.h"
#include "VxParse.h"
#include "memory.h"
#ifndef WIN_PLATFORM
	#include <sys/types.h> 
	#include <sys/syscall.h> 
	#include <unistd.h>
#endif // WIN_PLATFORM

//============================================================================--
//! get the thread id of current thread that is executing
unsigned int VxGetCurrentThreadId( void )
{
	#ifdef WIN_PLATFORM
		return GetCurrentThreadId();
	#else // LINUX
		return (unsigned int)pthread_self();
	#endif
}
//============================================================================--

VX_THREAD_START_CALLBACK_T VxThread::m_funcStartCallback = NULL;
VX_THREAD_EXIT_CALLBACK_T VxThread::m_funcExitCallback = NULL;

//============================================================================--
//=== constructor ===//
VxThread::VxThread( void )
: m_bIsExitCallbackLocked(false)
, m_pvUserParam(0)
, m_u8ThreadFlags(0)
, m_uiThreadId(0)		// thread ID
#ifdef WIN_PLATFORM
, m_hThread(0)		    // handle to thread
#endif
{
}

//============================================================================--
//=== destructor ===//
VxThread::~VxThread( void )
{
	EndThread();
}

//============================================================================--
//! thread calls this to see if should exit
bool VxThread::isAborted( void )
{ 
	return ( (0 != (m_u8ThreadFlags & VX_FLAG_THREAD_ABORT)) || VxIsAppShuttingDown());
}

//============================================================================--
void VxThread::Abort( bool bAbort )
{
	if( bAbort || VxIsAppShuttingDown() )
		m_u8ThreadFlags |= VX_FLAG_THREAD_ABORT;
	else
		m_u8ThreadFlags &= ~VX_FLAG_THREAD_ABORT;
}

//============================================================================--
//! set function that will be called when any thread is started
void VxThread::SetThreadStartCallback( VX_THREAD_START_CALLBACK_T func )
{
	m_funcStartCallback = func;
}

//============================================================================--
//! set function that will be called when any thread is about to exit
void VxThread::SetThreadExitCallback( VX_THREAD_EXIT_CALLBACK_T func )
{
	m_funcExitCallback = func;
}

//============================================================================--
//! start a thread
RCODE VxThread::StartThread(	VX_THREAD_FUNCTION_T	pfuncThreadFunc,	// function that thread calls
								void *					pvUserParam, 		// caller defined param
								const char *			pThreadName,		// thread name
								int						iExtraStackSpace )	// will be added to minimum stack size	
{
	vx_assert( false == this->IsThreadCreated() );
	m_pvUserParam = pvUserParam;
	Abort( false );
	SetIsThreadCreated( true );
	SetIsThreadRunning( true );

	if( pThreadName )
	{
		m_strThreadName = pThreadName;
	}
	else
	{
		StdStringFormat( m_strThreadName, "Thread 0x%x", this );
	}
	

	#ifdef WIN_PLATFORM
		// start a windows thread
		m_hThread = CreateThread(	0,					// security attributes
									0,					// stack size
									(LPTHREAD_START_ROUTINE)pfuncThreadFunc, // thread function
									(void *)this,  		// parameter to pass to thread function
									0,					// thread creation flags
									&m_uiThreadId);		// return thread id
		if( NULL == m_hThread )
		{
			m_u8ThreadFlags = 0;
			log_msg( LOG_FATAL, "VxThread:: ERROR.. FAILED TO CREATE THREAD\n" );
			abort();
			return -1;
		}
		log_msg( 0, "VxThread:Created Thread %s\n", m_strThreadName.c_str() );
	#else // LINUX
		int iErr;
		if( 0 != ( iErr = pthread_attr_init(&m_oThreadAttr) ) )
		{
			log_msg( LOG_FATAL, "VxThread:pthread_attr_init FAILED %d %d\n", VxGetLastError(), iErr );
		}
		else
		{
			size_t sizetStackSize = 0;
			iErr = pthread_attr_getstacksize(&m_oThreadAttr, &sizetStackSize);
			if( iErr )
			{
				log_msg( LOG_FATAL, "VxThread:pthread_attr_getstacksize error %d\n", iErr );
			}
			else
			{
				log_msg( LOG_DEBUG, "VxThread:Created Thread %s min stack size %d\n", m_strThreadName.c_str(), sizetStackSize );
				if( iExtraStackSpace )
				{
					sizetStackSize += iExtraStackSpace;
					iErr = pthread_attr_setstacksize(&m_oThreadAttr, sizetStackSize);
					if( iErr )
					{
						log_msg( LOG_FATAL, "VxThread:pthread_attr_setstacksize error %d\n", iErr );
					}
				}
			}
			iErr = pthread_attr_setdetachstate(&m_oThreadAttr, PTHREAD_CREATE_DETACHED);
			if( iErr )
			{
				log_msg( LOG_FATAL, "VxThread:pthread_attr_setdetachstate error %d\n", iErr );
			}
		}
		memset( &m_oThreadInfo, 0, sizeof( m_oThreadInfo ) );

		if( 0 < ( iErr = pthread_create(	&m_oThreadInfo,		// thread structure
											&m_oThreadAttr,		// thread attributes
											pfuncThreadFunc,	// thread function
											(void *)this) ) )	// parameter to pass to thread function
		{
			m_u8ThreadFlags = 0;
			log_msg( LOG_FATAL, "VxThread:pthread_create FAILED %d %d\n", VxGetLastError(), iErr );
#ifdef WIN_PLATFORM
			// android doesnt seem to have a abort
			abort();
#endif // ANDROID_PLATFORM
			return -1;
		}
    #endif

	return 0;
}

//============================================================================--
const char * VxThread::GetThreadName( void )
{
	return m_strThreadName.c_str();
}

//============================================================================--
//! get the user specified parameter
void * VxThread::GetUserParam( void )
{
	return m_pvUserParam;
}

//============================================================================--
UINT VxThread::GetThreadId( void )
{
	return m_uiThreadId;
}

//============================================================================--
bool VxThread::IsThreadCreated( void )			
{ 
	return (m_u8ThreadFlags & VX_FLAG_THREAD_CREATED) ? true : false;
}

//============================================================================--
bool VxThread::IsThreadRunning( void )			
{ 
	return (m_u8ThreadFlags & VX_FLAG_THREAD_RUNNING) ? true : false;
}

//============================================================================--
void VxThread::SetIsThreadCreated( bool bIsCreated )
{ 
	if( bIsCreated) m_u8ThreadFlags |= VX_FLAG_THREAD_CREATED;
	else m_u8ThreadFlags &= ~VX_FLAG_THREAD_CREATED;
}

//============================================================================--
//! thread calls this when first starts running
void VxThread::SetIsThreadRunning( bool bIsRunning )
{ 
	if( bIsRunning) m_u8ThreadFlags |= VX_FLAG_THREAD_RUNNING;
	else m_u8ThreadFlags &= ~VX_FLAG_THREAD_RUNNING;
	m_uiThreadId = VxGetCurrentThreadId();
	if( m_funcStartCallback )
	{
		//log_msg( LOG_DEBUG, "Thread 0x%x started with callback\n", m_uiThreadId );
		m_funcStartCallback( m_uiThreadId );
	}
	//else
	//{
	//	log_msg( LOG_DEBUG, "Thread 0x%x has no start callback\n", m_uiThreadId );
	//}
#ifndef WIN_PLATFORM
    #ifdef ANDROID_PLATFORM
        unsigned int tid = gettid();
        log_msg( LOG_DEBUG, "SetIsThreadRunning Thread %s id %d pid %d tid %d running %d\n",
            m_strThreadName.c_str(),
            m_uiThreadId,
            getpid(),
            tid,
            bIsRunning );
    #endif// ANDROID_PLATFORM
#endif // WIN_PLATFORM
}

//============================================================================--
bool VxThread::isThreadEndCallbackLocked( void )
{
	return m_bIsExitCallbackLocked;
}

//============================================================================--
void VxThread::setIsThreadEndCallbackLocked( bool bIsLocked )
{
	m_bIsExitCallbackLocked = bIsLocked;
}

//============================================================================--
//! Thread calls this just before exit
void VxThread::ThreadAboutToExit( bool bExitThreadNow )  
{
	log_msg( LOG_THREAD, "ThreadAboutToExit %s %d %d\n", m_strThreadName.c_str(), m_uiThreadId, VxGetCurrentThreadId() );
	if( m_funcExitCallback )
	{
		log_msg( LOG_DEBUG, "Thread 0x%x 0x%x exiting with callback\n", m_uiThreadId, VxGetCurrentThreadId() );
		m_funcExitCallback( m_uiThreadId, m_bIsExitCallbackLocked );
	}

	m_u8ThreadFlags &= ~VX_FLAG_THREAD_RUNNING; 
	m_u8ThreadFlags |= VX_FLAG_THREAD_DONE; 
	SetIsThreadCreated( false );
	log_msg( LOG_DEBUG, "Thread 0x%x 0x%x thread exit\n", m_uiThreadId, VxGetCurrentThreadId() );
	Abort( false ); // reset abort flag
	#ifndef WIN_PLATFORM
		if( bExitThreadNow )
		{
			// linux exit thread
			pthread_exit(0);
		}
	#endif // WIN_PLATFORM
	log_msg( LOG_DEBUG, "Thread 0x%x 0x%x thread exit done\n", m_uiThreadId, VxGetCurrentThreadId() );
}

//============================================================================--
//! end a thread
RCODE VxThread::EndThread( void )
{
    Abort( true ); // tell thread to abort
	if( ( m_u8ThreadFlags & VX_FLAG_THREAD_RUNNING ) && (0 == ( m_u8ThreadFlags & VX_FLAG_THREAD_DONE )) )
	{
		int iCnt = 0;
		while( IsThreadRunning() )
		{
			VxSleep( 10 );
			iCnt++;
			if( iCnt > 200 )
			{
				// timeout and exit
				log_msg( 0, "VxThread:EndThread: timeout of Thread %s\n", GetThreadName() );
				return -1;
			}
		}
	}

	return 0;
}


